import { BROWSIX_SHM_SIZE } from "./constants";
import { SyscallResponse, syscall } from "./syscall";

export interface Environment {
    [name: string]: string;
}
const _perfomancePolyfill = () => {
    // based on https://gist.github.com/paulirish/5438650 copyright Paul Irish 2015.
    if (!("performance" in self)) {
        // @ts-ignore
        (self.performance as any) = {};
    }

    Date.now =
        Date.now ||
        (() => {
            // thanks IE8
            return new Date().getTime();
        });

    if ("now" in self.performance === false) {
        let nowOffset = Date.now();

        if (performance.timing && performance.timing.navigationStart) {
            nowOffset = performance.timing.navigationStart;
        }

        // @ts-ignore
        self.performance.now = () => Date.now() - nowOffset;
    }
};

const _hrtime = (previousTimestamp?: [number, number]): [number, number] => {
    _perfomancePolyfill();
    const baseNow = Math.floor((Date.now() - performance.now()) * 1e-3);
    const clocktime = performance.now() * 1e-3;
    let seconds = Math.floor(clocktime) + baseNow;
    let nanoseconds = Math.floor((clocktime % 1) * 1e9);

    if (previousTimestamp) {
        seconds = seconds - previousTimestamp[0];
        nanoseconds = nanoseconds - previousTimestamp[1];
        if (nanoseconds < 0) {
            seconds--;
            nanoseconds += 1e9;
        }
    }
    return [seconds, nanoseconds];
};

const NS_PER_SEC: number = 1e9;
_hrtime.bigint = (time?: [number, number]): bigint => {
    const diff = _hrtime(time);
    return (diff[0] * NS_PER_SEC + diff[1]) as unknown as bigint;
};

//const PER_BLOCKING = 0x80;

// one-shot event emitter.  Used to let parts of the GopherJS runtime
// block until we get an 'init' message from the Browsix kernel
// containing the argv vector and our environment.
class OnceEmitter {
    listeners: { [n: string]: Function[] };

    constructor() {
        this.listeners = {};
    }

    once(event: string, cb: Function): void {
        let cbs = this.listeners[event];
        if (!cbs)
            cbs = [cb];
        else
            cbs.push(cb);
        this.listeners[event] = cbs;
    }

    emit(event: string, ...args: any[]): void {
        let cbs = this.listeners[event];
        this.listeners[event] = [];
        if (!cbs)
            return;
        for (let i = 0; i < cbs.length; i++) {
            cbs[i].apply(null, args);
        }
    }
}



export class Process extends OnceEmitter {
    //memory: SharedArrayBuffer;
    //heap: SharedArrayBuffer = null
    //waitOff: number = 0
    //syscall;
    //pid;
    argv: string[];
    env: Environment;

    hrtime = _hrtime.bigint
    constructor() {
        super();
        this.argv = null;
        this.env = null;

        // WebAssembly controls the memory completely, so we copy
        // values used by Browsix into a SharedArrayBuffer
        // and copy them back after we are done
        //this.heap = new SharedArrayBuffer(BROWSIX_SHM_SIZE)
        //this.memory = new SharedArrayBuffer(BROWSIX_SHM_SIZE);

        // this.memory = new WebAssembly.Memory({
        //     initial: 1024,
        //     maximum: 1024,
        //     // @ts-ignore
        //     shared: true,
        // });

        //this.waitOff = 0

        syscall.addEventListener('init', init.bind(this));

        function init(data: SyscallResponse): void {
            // 0: args
            // 1: environ
            // 2: debug flag
            // 3: pid (if fork)
            // 4: heap (if fork)
            // 5: fork args (if fork)

            let args = data.args[0];
            let environ = data.args[1];
            //args = [args[0]].concat(args);


            this.argv = args
            this.env = environ
            // if (typeof SharedArrayBuffer !== 'function') {
            // 	main(args, environ);
            // 	return;
            // }

            syscall.personality(personalityChanged.bind(this));

        }

        function personalityChanged(err: any): void {
            if (err) {
                console.log('personality: ' + err);
                return;
            }
            console.log('now in blocking mode.');
            setTimeout(() => { this.emit('ready'); }, 0);
        }
    }

    exit(code = 0): void {
        //syscall.exit(code);
    }
}


